package com.haibara.codesubmiteval.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haibara.codesubmiteval.common.ErrorCode;
import com.haibara.codesubmiteval.constant.CommonConstant;
import com.haibara.codesubmiteval.constant.RedisKeyConstant;
import com.haibara.codesubmiteval.exception.ThrowUtils;
import com.haibara.codesubmiteval.mapper.QuestionCaseMapper;
import com.haibara.codesubmiteval.mapper.QuestionMapper;
import com.haibara.codesubmiteval.mapper.UserMapper;
import com.haibara.codesubmiteval.model.dto.question.QuestionAddRequest;
import com.haibara.codesubmiteval.model.dto.question.QuestionEditRequest;
import com.haibara.codesubmiteval.model.dto.question.QuestionQueryRequest;
import com.haibara.codesubmiteval.model.entity.Question;
import com.haibara.codesubmiteval.model.entity.QuestionCase;
import com.haibara.codesubmiteval.model.vo.QuestionVO;
import com.haibara.codesubmiteval.model.vo.Top10VO;
import com.haibara.codesubmiteval.service.QuestionService;
import com.haibara.codesubmiteval.utils.RedisRecoveryUtil;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @author AiChan
 * @description 针对表【question(题目)】的数据库操作Service实现
 * @createDate 2023-12-20 13:17:24
 */
@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question>
        implements QuestionService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisRecoveryUtil redisRecoveryUtil;
    @Resource
    private QuestionCaseMapper questionCaseMapper;

    // region 题目增删改查 start
    @Override
    public void addQuestion(QuestionAddRequest questionAddRequest) {
        List<String> tags = questionAddRequest.getTags();
        Question question = new Question();
        BeanUtil.copyProperties(questionAddRequest, question);
        if (tags != null && !tags.isEmpty()) {
            String tagsJsonStr = JSONUtil.toJsonStr(tags);
            question.setTags(tagsJsonStr);
        }
        if (question.getScore() == null) {
            question.setScore(0);
        }
        question.setRatio(0);
        boolean save = this.save(question);
        ThrowUtils.throwIf(!save, ErrorCode.SYSTEM_ERROR, "新增题目失败");
        // 修改redis中的值
        stringRedisTemplate.opsForValue().increment(RedisKeyConstant.questionNumber, 1);
        // 删除Redis中可能存在的Question空值
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, question.getId().toString()));
    }


    @Override
    public void updateQuestion(QuestionEditRequest questionEditRequest) {
        Question question = new Question();
        BeanUtil.copyProperties(questionEditRequest, question);
        List<String> tags = questionEditRequest.getTags();
        if (tags != null && !tags.isEmpty()) {
            String tagsJsonStr = JSONUtil.toJsonStr(tags);
            question.setTags(tagsJsonStr);
        }
        Question originQuestion = this.getById(question.getId());
        boolean b = this.updateById(question);
        ThrowUtils.throwIf(!b, ErrorCode.SYSTEM_ERROR, "修改题目失败 : " + question.getId());
        Integer score = questionEditRequest.getScore();
        if (score != null && !score.equals(originQuestion.getScore())) {
            // 修改题目分数带来的连带修改
            // 使用SCAN命令获取所有匹配的键
            Set<String> keys = stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
                Set<String> keysTmp = new HashSet<>();
                ScanOptions options = ScanOptions.scanOptions().match(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, "*")).count(100).build();
                Cursor<byte[]> cursor = connection.scan(options);
                while (cursor.hasNext()) {
                    keysTmp.add(new String(cursor.next(), StandardCharsets.UTF_8));
                }
                return keysTmp;
            });
            ThrowUtils.throwIf(keys == null, ErrorCode.SYSTEM_ERROR, "Redis宕机 - updateQuestion : " + questionEditRequest);
            // 获取受影响的用户id
            List<Long> affectedUserId = new ArrayList<>();
            for (String key : keys) {
                // 提取用户ID，例如从"acceptedQuestionIds:1"中提取"1"
                String userId = key.substring(key.lastIndexOf(":") + 1);
                Boolean affected = stringRedisTemplate.opsForSet().isMember(
                        RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, userId),
                        question.getId().toString()
                );
                if (Boolean.TRUE.equals(affected)) {
                    affectedUserId.add(Long.parseLong(userId));
                }
            }
            if (!affectedUserId.isEmpty()) {
                // 处理排行榜
                int deltaScore = score - originQuestion.getScore();
                // 获取普通用户ids
                List<Long> commonUserIds = userMapper.getCommonUserIdByIsList(affectedUserId);
                for (Long userId : commonUserIds) {
                    Top10VO top10VO = Top10VO.builder().userId(userId).build();
                    stringRedisTemplate.opsForZSet().incrementScore(
                            RedisKeyConstant.top10,
                            JSONUtil.toJsonStr(top10VO),
                            deltaScore
                    );
                }
                // 处理数据库中用户分数
                userMapper.batchUpdateScore(deltaScore, affectedUserId);
            }
        }
        // 删除Question缓存
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, question.getId().toString()));
    }

    @Override
    public void deleteQuestion(Long questionId) {
        // 获取分数
        Question question = this.getById(questionId);
        Integer score = question.getScore();
        boolean b = this.removeById(questionId);
        ThrowUtils.throwIf(!b, ErrorCode.SYSTEM_ERROR, "删除题目失败 : " + questionId);
        // 修改Redis
        stringRedisTemplate.opsForValue().increment(RedisKeyConstant.questionNumber, -1);
        // 处理题目分数带来的影响
        if (score != 0) {
            // 使用SCAN命令获取所有匹配的键
            Set<String> keys = stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
                Set<String> keysTmp = new HashSet<>();
                ScanOptions options = ScanOptions.scanOptions().match(RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, "*")).count(100).build();
                Cursor<byte[]> cursor = connection.scan(options);
                while (cursor.hasNext()) {
                    keysTmp.add(new String(cursor.next(), StandardCharsets.UTF_8));
                }
                return keysTmp;
            });
            ThrowUtils.throwIf(keys == null, ErrorCode.SYSTEM_ERROR, "Redis宕机 - deleteQuestion : " + questionId);
            // 获取受影响的用户id
            List<Long> affectedUserId = new ArrayList<>();
            for (String key : keys) {
                // 提取用户ID，例如从"acceptedQuestionIds:1"中提取"1"
                String userId = key.substring(key.lastIndexOf(":") + 1);
                Boolean affected = stringRedisTemplate.opsForSet().isMember(
                        RedisKeyConstant.getCompleteKey(RedisKeyConstant.userAcceptedQuestionIdsSet, userId),
                        questionId.toString()
                );
                if (Boolean.TRUE.equals(affected)) {
                    affectedUserId.add(Long.parseLong(userId));
                }
            }
            if (!affectedUserId.isEmpty()) {
                // 处理排行榜
                int deltaScore = -score;
                List<Long> commonUserIds = userMapper.getCommonUserIdByIsList(affectedUserId);
                for (Long userId : commonUserIds) {
                    Top10VO top10VO = Top10VO.builder().userId(userId).build();
                    stringRedisTemplate.opsForZSet().incrementScore(
                            RedisKeyConstant.top10,
                            JSONUtil.toJsonStr(top10VO),
                            deltaScore
                    );
                }
                // 处理数据库中用户分数
                userMapper.batchUpdateScore(deltaScore, affectedUserId);
            }
        }
        stringRedisTemplate.delete(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, question.getId().toString()));
        // 删除测试用例
        int delete = questionCaseMapper.delete(
                new LambdaQueryWrapper<QuestionCase>()
                        .eq(QuestionCase::getQuestionId, questionId)
        );
        ThrowUtils.throwIf(delete == 0, ErrorCode.SYSTEM_ERROR, "删除测试用例失败 : " + questionId);
    }

    @Override
    public Page<Question> pageQueryQuestion(QuestionQueryRequest questionQueryRequest) {
        Long id = questionQueryRequest.getId();
        String title = questionQueryRequest.getTitle();
        List<String> tags = questionQueryRequest.getTags();
        Long userId = questionQueryRequest.getUserId();
        long current = questionQueryRequest.getCurrent();
        long pageSize = questionQueryRequest.getPageSize();
        String sortField = questionQueryRequest.getSortField();
        String sortOrder = questionQueryRequest.getSortOrder();

        // 构建查询条件
        LambdaQueryWrapper<Question> lqw = new LambdaQueryWrapper<>();
        // 过滤
        lqw.eq(id != null, Question::getId, id)
                .like(!StrUtil.isBlankIfStr(title), Question::getTitle, title)
                .eq(userId != null, Question::getUserId, userId);
        // 进行tags模糊匹配
        if (tags != null && !tags.isEmpty()) {
            for (String tag : tags) {
                lqw.like(!StrUtil.isBlankIfStr(tag), Question::getTags, "\"" + tag + "\"");
            }
        }
        // 排序
        if (!StrUtil.isBlankIfStr(sortField)) {
            // 根据创建时间排序
            lqw.orderBy("createTime".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getCreateTime);
            // 根据更新时间排序
            lqw.orderBy("updateTime".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getUpdateTime);
            // 根据题目id
            lqw.orderBy("id".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getId);
            // 根据题目通过率排序
            lqw.orderBy("ratio".equals(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC), Question::getRatio);
        }
        // 分页
        return this.page(new Page<>(current, pageSize), lqw);
    }

    @Override
    public Page<QuestionVO> pageQueryQuestionVO(QuestionQueryRequest questionQueryRequest) {
        Page<Question> questionPage = this.pageQueryQuestion(questionQueryRequest);
        Page<QuestionVO> result = new Page<>();
        BeanUtil.copyProperties(questionPage, result);
        result.setRecords(
                questionPage.getRecords().stream().map(
                        QuestionVO::getQuestionVOByQuestion
                ).toList()
        );
        return result;
    }

    @Override
    public QuestionVO getQuestionVO(Long id) {
        // 先尝试从Redis获取
        String questionStr = stringRedisTemplate.opsForValue().get(RedisKeyConstant.getCompleteKey(RedisKeyConstant.questionKey, id.toString()));
        if (questionStr == null || StrUtil.isBlankIfStr(questionStr)) {
            questionStr = redisRecoveryUtil.recoveryQuestion(id);
        }
        // 空值
        ThrowUtils.throwIf(RedisKeyConstant.NoneValue.equals(questionStr), ErrorCode.NOT_FOUND_ERROR, "题目不存在 : " + id);
        Question question = JSONUtil.toBean(questionStr, Question.class);
        return QuestionVO.getQuestionVOByQuestion(question);
    }
}




